//-----------------------------------------------------------------------
// <copyright file="MergeByRef.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Vivek Adholia</author>
// <email>vadholia</email>
// <date>2005-11-30</date>
// <summary>
// Merges two itemlists together based on a reference metadata. 
// of items.
// </summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks
{
    #region Using directives
    using System;
    using System.Text;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using Microsoft.Build.BuildEngine;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    using Microsoft.Build.Shared;
    #endregion

    /// <summary>
    /// Takes two item lists as input and merges them together based on a reference metadata.
    /// This is similar to merging two DB tables together based on a reference key to generate a view.
    /// 
    /// The output of this task is a copy of ItemList1 with additional metadata for the first item that matched
    /// in itemlist2. All of the metadata is copied over and the identity of the item in ItemList2 becomes
    /// ItemList2_Identity.
    /// 
    /// Note that if ItemList1 and ItemList2 have the same metadata names then the value from 2 will be kept
    /// (normally other than Identity this would not be expected input)
    /// 
    /// If RemoveNonMatchingItems is set to true then the result contains only items where a match occured.
    /// This defaults to false.
    /// </summary>
    /// <remarks>
    /// <code><![CDATA[<MergeByRef ItemList1="@(list1)" MetaDataName1="Prop1" ItemList2="@(list2)" MetaDataName2="Prop2" RemoveNonMatchingItems="removeNonMatchingItems">
    ///                 <Output TaskParameter="MergedList" ItemName="MergedList" />
    ///                </MergeByRef>]]></code>
    /// <para>where:</para>
    /// <para><i>list1 (Required)</i></para>
    /// <para>The first ItemList to be merged</para>
    /// <para><i>list2 (Required)</i></para>
    /// <para>The second ItemList to be merged</para>
    /// <para><i>Prop1 (Required)</i></para>
    /// <para>The first ItemList's metadata</para>
    /// <para><i>Prop2 (Required)</i></para>
    /// <para>The second ItemList's metadata which maps to Prop1 metadata on the first Itemlist</para>
    /// <para><i>MergedList (Output)</i></para>
    /// <para>The merged ItemList </para>
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <Target Name="Test" >
    ///         <MergeByRef
    ///             ItemList1="@(AllAssemblies)"
    ///             ItemList2="@(Solutions)"/>
    ///             MetaDataName1="SolutionName"
    ///             MetaDataName2="Identity"
    ///             RemoveNonMatchingItems="true"/>
    ///                 <Output TaskParameter="MergedList" PropertyName="AssembliesFull" />
    ///         </ShortenPath>
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>
    public class MergeByRef : TaskBase
    {
        #region member variables
        private ITaskItem[] itemList1;
        private ITaskItem[] itemList2;
        private string metadataName1;
        private string metadataName2;
        private ITaskItem[] mergedList;
        private bool removeNonMatchingItems = false;
        #endregion

        /// <summary>
        /// MergeByRef
        /// </summary>
        public MergeByRef()
        {
        }

        #region properties

        /// <summary>
        /// he first ItemList to be merged 
        /// </summary>
        [Required]
        public ITaskItem[] ItemList1
        {
            get { return this.itemList1; }
            set { this.itemList1 = value; }
        }

        /// <summary>
        /// The second ItemList to be merged 
        /// </summary>
        [Required]
        public ITaskItem[] ItemList2
        {
            get { return this.itemList2; }
            set { this.itemList2 = value; }
        }

        /// <summary>
        /// Normally this task returns a copy of ItemList1
        /// </summary>
        public bool RemoveNonMatchingItems
        {
            get { return this.removeNonMatchingItems; }
            set { this.removeNonMatchingItems = value; }
        }

        /// <summary>
        /// The first ItemList's metadata which maps to metadata2 on ItemList2 
        /// </summary>
        [Required]
        public string MetaDataName1
        {
            get { return this.metadataName1; }
            set { this.metadataName1 = value; }
        }

        /// <summary>
        /// The second ItemList's metadata which maps to Prop1 metadata on the first Itemlist 
        /// </summary>
        [Required]
        public string MetaDataName2
        {
            get { return this.metadataName2; }
            set { this.metadataName2 = value; }
        }

        /// <summary>
        /// The merged ItemList output is normally a copy of ItemList1 plus additional matching metadata
        /// If this paramater is set to true then the output list does not contain entries from itemlist one
        /// for which no match was found.
        /// </summary>
        [Output]
        public ITaskItem[] MergedList
        {
            get { return this.mergedList; }
            set { this.mergedList = value; }
        }

        #endregion

        /// <summary>
        /// InternalExecute
        /// </summary>
        protected override void InternalExecute()
        {
            //Create a new ItemList
            int itemListOneCount = itemList1.Length;
            int matchedItems = 0;
            mergedList = new ITaskItem[itemListOneCount];
            List<bool> matchedItemPostions = new List<bool>(itemListOneCount);

            for (int ii = 0; ii < itemListOneCount; ii++)
            {
                mergedList[ii] = new TaskItem(itemList1[ii].ItemSpec, itemList1[ii].CloneCustomMetadata());

                ITaskItem item2 = FindMatchingItemInSecondList(itemList1[ii].GetMetadata(metadataName1));
                if (item2 != null)
                {
                    mergedList[ii].SetMetadata("ItemList2_Identity", item2.ItemSpec);

                    //Get custom metadata dictionary for itemlist2.
                    IDictionary dict = item2.CloneCustomMetadata();

                    //Iterate through dictionary and prefix ItemList2_ to metadatanames
                    foreach (object key in dict.Keys)
                    {
                        string metadataName = key.ToString();
                        string value = dict[key].ToString();
                        mergedList[ii].SetMetadata(metadataName, value);
                    }
                    matchedItemPostions.Add(true);
                    matchedItems++;
                }
                else
                {
                    matchedItemPostions.Add(false);
                }
             }

             // Remove non matching items if requested
             if (matchedItemPostions.Contains(false) && removeNonMatchingItems)
             {
                 if (!matchedItemPostions.Contains(true))
                 {
                     // No Matches
                     mergedList = null;
                 }
                 else
                 {

                     // Unfortunately Item lists cannot have items removed so we need to copy to a new list
                     ITaskItem[] mergedListMinusNoMatching = new ITaskItem[matchedItems];
                     int mergedListPosition = 0;

                     for (int ii = 0; ii < itemListOneCount; ii++)     
                     {
                         if (matchedItemPostions[ii] == true)
                         {
                             mergedListMinusNoMatching[mergedListPosition++] = mergedList[ii];
                         }
                     }
                     mergedList = mergedListMinusNoMatching;
                 }
             }
        }

        private ITaskItem FindMatchingItemInSecondList(string metadata1)
        {
            for (int ii = 0; ii < itemList2.Length; ii++)
            {
                if (itemList2[ii].GetMetadata(metadataName2) == metadata1)
                    return itemList2[ii];
            }
            return null;
        }
    }
}
